#include "note.h"
#include "debug.h"
#include "settings.h"
#include "unicode.h"

bool GetEndDate(const wxString& date, wxString *result)
{
    int y,m,d;
    *result = "";
    if (!ExtractDate(date, &y, &m, &d)) {
        dbg("GetEndDate(%s) fail due to ExtractDate()\n", date);
        return false;
    }
    if (y == 0) {
        dbg("GetEndDate(%s) fail due to y == 0\n", date);
        return false;
    }
    if (m == 0) {
        result->Printf("%04u", y+1);
        return true;
    }
    if (d == 0) {
        result->Printf("%04u-%02u", y, m+1);
        return true;
    }
    result->Printf("%04u-%02u-%02u", y, m, d+1);
    return true;
}

bool ExtractDate(const wxString& date, int *year, int *month, int *day)
{
    long y=0,m=0,d=0;
    wxString parsed_date;

    *year = -1;
    *month = -1;
    *day = -1;

    if (!ParseDate(date, parsed_date)) {
        dbg("ExtractDate failed due to ParseDate()\n");
        return false;
    }

    if (parsed_date.IsEmpty())
        return false;

    if (parsed_date.Length() >= 4) {
        wxCHECK(parsed_date.Mid(0, 4).ToLong(&y), 0);
        *year = (int)y;
    }

    if (parsed_date.Length() >= 6) {
        wxCHECK(parsed_date.Mid(5, 2).ToLong(&m), 0);
        *month = (int)m;
    }

    if (parsed_date.Length() >= 9) {
        wxCHECK(parsed_date.Mid(8, 2).ToLong(&d), 0);
        *day = (int)d;
    }

    return true;
}

bool Note::ExtractDate(int *y, int *m, int *d) const
{
    return ::ExtractDate(this->date, y, m, d);
}

wxString Note::FormatForDisplay() const
{
    wxString s;
    if (!date.IsEmpty())
        s = wxString::Format("%s\n\n", date);
    s += wxString::Format("%s", text);

    //delete ending whitespace. EOL at the end of note text causes trouble with GTK wxTextCtrl
    //line number determining in NotesPanelBase
    while (s.Len() && s[s.Len()-1] == '\n') s.Truncate(s.Len()-1);

    return s;
}

wxString Note::ToString() const
{
    wxString s = wxString::Format("Note(id=%d, date=%s)", id, date.wc_str());
    return s;
}

bool Note::MatchesBody(wxString body) const
{
    if (body.IsEmpty())
        return true;
    wxString body_upper = this->text;
    body_upper.MakeUpper();
    body.MakeUpper();
    return (body_upper.Find(body) != -1);
}

bool CheckDate(const wxString& fromwx)
{
    wxString temp;
    return ParseDate(fromwx, temp);
}

bool ParseDate(const wxString& arg_date, wxString& result)
{
    int year=0, month=0, day=0, sort=0;
    bool has_month=false, has_day=false, has_sort=false;
    wxString date = arg_date;
    result.Clear();

    // accept empty date
    if (date.IsEmpty()) {
        result = wxT("");
        return true;
    }

    // ignored prefixes
    static const wchar_t* ignored_prefixes[] = {
        L"asi ",
        L"tusim ",
        L"mozno ",
        L"zrejme "
    };
    #define ignored_prefixes_count (sizeof(ignored_prefixes)/sizeof(ignored_prefixes[0]))
    for (unsigned x=0; x<ignored_prefixes_count; x++)
        if (date.StartsWith(ignored_prefixes[x])) {
            date.Remove(0, wxStrlen(ignored_prefixes[x]));
            break;
        }

    // prefixes
    static struct {const wchar_t* prefix; int sort;} prefixes[] = {
        {L"pred ",          1},
        {L"hned pred ",     2},
        {L"coskoro pred ",  2},
        {L"do ",            3},
        {L"pocas ",         6},
        {L"okolo ",         6},
        {L"asi ",           6},
        {L"od ",            7},
        {L"hned po ",       8},
        {L"coskoro po ",    8},
        {L"po ",            9}
    };
    #define prefixes_count (sizeof(prefixes)/sizeof(prefixes[0]))
    for (unsigned x=0; x<prefixes_count; x++)
        if (date.StartsWith(prefixes[x].prefix)) {
            sort = prefixes[x].sort;
            has_sort = true;
            date.Remove(0, wxStrlen(prefixes[x].prefix));
            break;
        }

    // get year
    if (date.Len()<4 || !(isdigit(date[0]) && isdigit(date[1]) && isdigit(date[2]) && isdigit(date[3])))
        return false;
    year = 1000*((char)date[0]-'0') + 100*((char)date[1]-'0') + 10*((char)date[2]-'0') + (char)date[3]-'0';
    date.Remove(0, 4);

    // allow end or space or 's' (eg. 1930s)
    if (date.IsEmpty() || date[0] == ' ' || date[0] == 's')
        goto done;

    // otherwise expect '-'
    if (date[0] != '-')
        return false;
    date.Remove(0,1);

    // allow "??"
    if (date.StartsWith(wxT("??")))
        goto done;


    // named months
    static struct {const wchar_t* name; int sort;} months[] = {
        {L"zaciatkom", 0},
        {L"zaciatok",  0},
        {L"polovica",  6},
        {L"koncom",    13},
        {L"koniec",    13},
        {L"jar",       3},
        {L"leto",      6},
        {L"jesen",     9},
        {L"zima",      12}
    };
    #define months_count (sizeof(months)/sizeof(months[0]))
    for (unsigned x=0; x<months_count; x++)
        if (date.StartsWith(months[x].name)) {
            month = months[x].sort;
            has_month = true;
            goto done;
        }

    // followed by month
    if (date.Len()<2 || !(isdigit(date[0]) && isdigit(date[1])))
        return 0;
    month = 10*((char)date[0]-'0') + (char)date[1]-'0';
    date.Remove(0,2);
    has_month=true;

    // allow end
    if (date.IsEmpty() || date[0] == ' ')
        goto done;

    // otherwise expect '-'
    if (date[0] != '-')
        return 0;
    date.Remove(0,1);

    // allow "??"
    if (date.StartsWith(wxT("??")))
        goto done;

    // named days
    static struct {const wchar_t* name; int sort;} days[] = {
        {L"zaciatkom", 0},
        {L"zaciatok",  0},
        {L"polovica",  15},
        {L"koncom",    32},
        {L"koniec",    32},
    };
    #define days_count (sizeof(days)/sizeof(days[0]))
    for (unsigned x=0; x<days_count; x++)
        if (date.StartsWith(days[x].name)) {
            day = days[x].sort;
            has_day = true;
            goto done;
        }

    // followed by day
    if (date.Len()<2 && !(isdigit(date[0]) && isdigit(date[1])))
        return 0;
    day = 10*((char)date[0]-'0') + (char)date[1]-'0';
    date.Remove(0,2);
    has_day=true;

    // anything except digit can follow
    if (date.Len()>0 && isdigit(date[0]))
        return 0;

done:

    result += wxString::Format(wxT("%04d"), year);

    if (has_month)
        result += wxString::Format(wxT("-%02d"), month);
    else {
        result += wxT("-00-00");
        goto done2;
    }

    if (has_day)
        result += wxString::Format(wxT("-%02d"), day);
    else {
        result += wxT("-00");
        goto done2;
    }

done2:
    if (has_sort)
        result += wxString::Format(wxT("-%d"), sort);
    else
        result += wxT("-5");

    return 1;
}

wxString ParseDate(const wxString& s)
{
    wxString result;
    ParseDate(s, result);
    return result;
}

int Note::CompareDates(const wxString& date1, const wxString& date2)
{
    if (date1.IsEmpty() && date2.IsEmpty())
        return 0;
    if (date1.IsEmpty())
        return -1;
    if (date2.IsEmpty())
        return 1;
    wxString sort1 = ParseDate(date1);
    wxString sort2 = ParseDate(date2);
    int result=sort1.CompareTo(sort2);
    return result;
}

int Note::CompareDateTo(const wxString& date2) const
{
    return Note::CompareDates(this->date, date2);
}

int Note::CompareDateTo(const Note& note2) const
{
    return Note::CompareDates(this->date, note2.date);
}

wxString Note::ParseAllKeywords() const
{
    // WARN: try to keep error handling similar to one in TextLayout analyzer
    wxString keywords="";
    bool first_keyword=true;
    bool in_keyword=false, inline_keyword=false, invalid_keyword=false;
    int keyword_start=-1, keyword_end=-1;
    int len = (int)text.Len();
    for (int x=0; x<(int)text.Len(); x++) {
        if (text[x]=='{') {
            in_keyword=true;
            inline_keyword=false;
            if (x+1<len && text[x+1] == '{') {
                x++;
                keyword_start++;
                inline_keyword=true;
            }
            if (!inline_keyword)
                while (x+1<len && text[x+1]=='<') x++;
            keyword_start = x+1;
            continue;
        }
        if (text[x]=='}' && in_keyword) {
            in_keyword=false;
            keyword_end = x;
            if (inline_keyword) {
                if (x+1 < (int)text.Len() && text[x+1] == '}') {
                    x++;
                } else
                    continue;
            }
            if (invalid_keyword)
                break;
            if (!first_keyword) keywords += ", ";
            keywords += text.Mid(keyword_start, keyword_end - keyword_start);
            first_keyword=false;
        }
        if (in_keyword && !IsKeywordChar(text[x]) && !IsBlankChar(text[x]))
            invalid_keyword=true;
    }

    wxASSERT(CheckKeywordsString(manual_keywords));
    if (!first_keyword) keywords += ", ";
    keywords += manual_keywords;

    return keywords;
}
